/* 请注意，这个按键函数不适用多按键同时按下，但我相信在你理解了原理后很容易写出多按键同时按下的功能 */

/* 原理解释：
	 key_pad_scan()为矩阵按键扫描函数，
	 需要每隔一个消抖时间调用一次，
	 消抖时间一般为10ms，
	 每次调用会刷新key_value、key_count、key_state三个变量，
	 具体作用见下文 */
	 
/* 名词解释：
	 key_pad_scan()函数通常每隔10ms调用一次，
	 每次调用结束后会有以下四种情况：
	 “松手”：指没有按键按下的任意一次调用key_pad_scan()结束时刻，
	 “按下”：指按下按键且经过消抖后第一次调用key_pad_scan()结束时刻
	 “抬起”：指按键被松开后第一次调用key_pad_scan()结束时刻，
	 “按住”：指从“按下”直至“抬起”期间的任意一次调用key_pad_scan()结束时刻，
	 通常一个按键被按下后抬起的流程是：
	 松手->按下->按住->抬起->松手
	 其中按键消抖发生在按键按下时刻，此时key_count == 1，
	 消抖不通过的按键key_count不会超过1
	 所以按键功能通常在key_count > 1才允许生效 */
	 
/* 功能解释：
	 一个按键可以有几种功能：
	 “短按触发”：即按键按下后经过消抖即刻生效一次，
	             判断条件通常为 (key_value == ? && key_count == 2 && key_state == 1)
							                              ?表示按键键值，不用我多解释吧
							 通常用于按键按下即增减变量的数值或切换模式
							 
	 “长按触发”：即按键按下后经过一段时间后生效一次，
	             判断条件通常为 (key_value == ? && key_count == n && key_state == 1)
	                                                            n表示即长按n个10ms后生效一次
							 通常用于长按一段时间后执行某个功能
							 
	 “短按抬起触发”：即按键按下后在规定时间内抬起即生效一次
	             判断条件通常为 (key_value == ? && key_count <= n && key_state == 2)
	             除了题目规定外，通常与“长按触发”结合使用，一般不单独用这种功能
							 
	 “长按抬起触发”：即按键按下后在规定时间后抬起即生效一次
	             判断条件通常为 (key_value == ? && key_count > n && key_state == 2)
	             除了题目规定外，一般不用这种功能
							 
	 “按住执行”：即按键按下经过消抖后或按下经过一段时间后，持续生效，直至按键抬起
	             判断条件通常为 (key_value == ? && key_count > n && key_state == 1)
							                                               n为1时按键按下经过消抖后持续生效，
							                                               n大于1时按键按下经过n个10ms后持续生效，
							 通常用于按住某按键切换数码管的显示内容，松开则返回按住之前的显示内容
	 
	 “一键多用”：一般为以上的两种或几种功能结合使用，
	             拿B站的长按点赞一键三连举例，
							 点赞按钮短按抬起即为点赞（短按抬起触发），
							 如果按住不放经过一小段时间会开始播放三连动画（按住执行），
							 若三连动画播放未完成时抬起了按键，则三连失败
							 若按住直至动画播放完成则完成三连（长按触发），此时继续按住也不会再执行任何功能 */


/* ↓↓↓代码从这里开始↓↓↓ */
#include "key.h"

// key_value：按键键值，有效值为4 ~ 19，0为无按键按下，在按下、按住、抬起时均为有效
uint8_t key_value = 0;
/* key_count：按键计时，按下、按住、抬起过程中每次调用key_pad_scan()结束key_count自增1，
   按键消抖过程发生在key_count == 1时 */
uint8_t key_count = 0;
/* key_state：按键状态
	 0：无按键，即松开
   1：按下或按住
   2：抬起 */
uint8_t key_state = 0;

/* 键值查询表 */
uint8_t code key_pad_value_all[4][4] = 
{ //常用于矩阵按键
	{7, 11, 15, 19, },
	{6, 10, 14, 18, },
	{5, 9, 13, 17, },
	{4, 8, 12, 16, },
};
/* 一般都是10ms左右调用一次，
	 10ms为消抖时间，可以根据需要更改
	 此外，这个按键函数为了方便理解，所以略显复杂臃肿，
	 可根据原理自行删改（推荐三行代码） */
/* 矩阵按键 */
void key_pad_scan()
{
	uint8_t row = 255, column = 255; //行号与列号，255是默认值，即无效值
	
	/* 操作IO口，这里按每个位来操作的，实际上最好统一赋值 */
	P30 = 1; //第1行
	P31 = 1; //第2行
	P32 = 1; //第3行
	P33 = 1; //第4行
	
	P44 = 0; //第1列
	P42 = 0; //第2列
	P35 = 0; //第3列
	P34 = 0; //第4列
	
	if (~P3 & 0x0F) //如果有按键按下；这里可以翻译为P3取反后屏蔽掉高四位，有按键按下则这个结果就不为零
	{
		/* 行判断 */
		if (!P33) //第4行
			row = 3;
		else if (!P32) //第3行
			row = 2;
		else if (!P31) //第2行
			row = 1;
		else if (!P30) //第1行
			row = 0;
		
		/* 操作IO口 */
		P30 = 0; //第1行
		P31 = 0; //第2行
		P32 = 0; //第3行
		P33 = 0; //第4行
		
		P44 = 1; //第1列
		P42 = 1; //第2列
		P35 = 1; //第3列
		P34 = 1; //第4列
		
		/* 列判断 */
		if (!P44) //第1列
			column = 0;
		else if (!P42) //第2列
			column = 1;
		else if (!P35) //第3列
			column = 2;
		else if (!P34) //第4列
			column = 3;
		
		/* 键值判断 */
		key_value = key_pad_value_all[row][column];
		
		/* 按键计时 */
		if (key_count < 250)
			key_count++;
		
		/* 状态更新 */
		key_state = 1;
	}
	else
	{
		if (key_state == 1) //如果按键抬起
		{
			key_state = 2;
		}
		else //无按键按下，即松手
		{
			key_value = 0;
			key_count = 0;
			key_state = 0;
		}
	}
}
